﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.IO;

namespace JOATServer.SocietyFramework.Core
{
    /// <summary>
    /// Automaton that manages the network communication between automatons in the local application and the remote application.
    /// Author: Micu Andrei
    /// </summary>
    public class NetworkMessagingAutomaton : Automaton
    {

        protected static string CONNECTION_STATE = "CONNECTION_STATE";
        protected static string IDLE_STATE = "IDLE_STATE";
        protected static string SEND_MESSAGE_STATE = "SEND_MESSAGE_STATE";

        protected MessageRouter messageRouter;

        protected TcpListener tcpListener;
        protected TcpClient tcpClient;
        protected BinaryReader inputStream;
        protected BinaryWriter outputStream;

        protected Thread pullingThread;
        protected bool pullRunning;
        protected Thread pushingThread;
        protected bool pushRunning;

        protected MessageList messagesToSend;

        protected enum Errors { PULL_ERROR, PUSH_ERROR };

        public NetworkMessagingAutomaton(MessageRouter messageRouter)
        {
            this.messageRouter = messageRouter;
            this.currentStateName = CONNECTION_STATE;
            this.messagesToSend = new MessageList();
            this.pullRunning = false;
            this.pushRunning = false;
            this.tcpListener = new TcpListener(IPAddress.Any, SocietyInformation.RemoteMessagingPort);
            this.tcpListener.Start();

            states.Add(CONNECTION_STATE, new ConnectionState(this));
            states[CONNECTION_STATE].transition = new ConnectionTransition(this);

            states.Add(IDLE_STATE, new NullState(this));
            states[IDLE_STATE].transition = new IdleTransition(this);

            states.Add(SEND_MESSAGE_STATE, new SendMessageState(this));
            states[SEND_MESSAGE_STATE].transition = new SimpleTransition(this, IDLE_STATE);
        }

        /* CONNECTION_STATE:  Automaton tries to connect to the server*/
        protected class ConnectionState : State
        {
            public ConnectionState(BaseAutomaton owner) : base(owner) { }

            public override void StateCode()
            {
                NetworkMessagingAutomaton automaton = (NetworkMessagingAutomaton)Owner;
                try
                {
                    if (automaton.tcpClient != null)
                    {
                        automaton.messagesToSend.CancelRemoving();
                        automaton.tcpClient.Close();
                    }
                    MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                        new Message("NetworkMessagingAutomaton", "Waiting client connection for network messaging..."));
                    automaton.tcpClient = automaton.tcpListener.AcceptTcpClient();
                    if (automaton.tcpClient.Connected)
                    {
                        automaton.inputStream = new BinaryReader(automaton.tcpClient.GetStream(), System.Text.Encoding.UTF8);
                        automaton.outputStream = new BinaryWriter(automaton.tcpClient.GetStream(), System.Text.Encoding.BigEndianUnicode);

                        automaton.pullRunning = true;
                        automaton.pushRunning = true;

                        automaton.pullingThread = new Thread(() => { automaton.PullMessages(); });
                        automaton.pushingThread = new Thread(() => { automaton.PushMessages(); });

                        automaton.pullingThread.Name = "NetworkMessagingPullingThread";
                        automaton.pushingThread.Name = "NetworkMessagingPushingThread";

                        automaton.pullingThread.Start();
                        automaton.pushingThread.Start();

                        MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                            new Message("NetworkMessagingAutomaton", "Client connected for network messaging."));
                    }
                }
                catch (Exception e)
                {
                    automaton.tcpClient = null;
                }
            }
        }

        protected class ConnectionTransition : Transition
        {
            public ConnectionTransition(BaseAutomaton owner) : base(owner) { }

            public override string TransitionCode()
            {
                NetworkMessagingAutomaton automaton = (NetworkMessagingAutomaton)Owner;
                if (automaton.tcpClient != null)
                {
                    return IDLE_STATE;
                }
                else
                {
                    return CONNECTION_STATE;
                }
            }
        }

        /* IDLE_STATE: Automaton waits for a message from the MessageRouter or from the threads. */
        protected class IdleTransition : Transition
        {
            public IdleTransition(BaseAutomaton owner) : base(owner) { }

            public override string TransitionCode()
            {
                NetworkMessagingAutomaton automaton = (NetworkMessagingAutomaton)Owner;
                automaton.GetNextMessage();
                if (automaton.currentMessage.From.Equals("NetworkMessagingAutomaton"))
                {
                    if (automaton.currentMessage.Data.Equals(Errors.PULL_ERROR) ||
                        automaton.currentMessage.Data.Equals(Errors.PUSH_ERROR))
                    {
                        return CONNECTION_STATE;
                    }
                    else
                    {
                        return IDLE_STATE;
                    }
                }
                else
                {
                    return SEND_MESSAGE_STATE;
                }
            }
        }


        /* SEND_MESSAGE_STATE: Automaton adds the message from the MessageRouter to the list of messages to send. */
        protected class SendMessageState : State
        {
            public SendMessageState(BaseAutomaton owner) : base(owner) { }

            public override void StateCode()
            {
                NetworkMessagingAutomaton automaton = (NetworkMessagingAutomaton)Owner;
                automaton.messagesToSend.Add(automaton.currentMessage);
            }
        }

        protected void PullMessages()
        {
            try
            {
                int lengthFrom, lengthTo, lengthData;
                byte[] buffer;
                String stringTo, stringFrom, stringData;
                while (pullRunning)
                {
                    buffer = inputStream.ReadBytes(4);
                    lengthFrom = (int)buffer[0];
                    lengthFrom <<= 8;
                    lengthFrom |= (int)buffer[1];
                    lengthFrom <<= 8;
                    lengthFrom |= (int)buffer[2];
                    lengthFrom <<= 8;
                    lengthFrom |= (int)buffer[3];

                    buffer = inputStream.ReadBytes(4);
                    lengthTo = (int)buffer[0];
                    lengthTo <<= 8;
                    lengthTo |= (int)buffer[1];
                    lengthTo <<= 8;
                    lengthTo |= (int)buffer[2];
                    lengthTo <<= 8;
                    lengthTo |= (int)buffer[3];

                    buffer = inputStream.ReadBytes(4);
                    lengthData = (int)buffer[0];
                    lengthData <<= 8;
                    lengthData |= (int)buffer[1];
                    lengthData <<= 8;
                    lengthData |= (int)buffer[2];
                    lengthData <<= 8;
                    lengthData |= (int)buffer[3];

                    buffer = inputStream.ReadBytes(lengthFrom);
                    stringFrom = Encoding.UTF8.GetString(buffer);

                    buffer = inputStream.ReadBytes(lengthTo);
                    stringTo = Encoding.UTF8.GetString(buffer);

                    buffer = inputStream.ReadBytes(lengthData);
                    stringData = Encoding.UTF8.GetString(buffer);

                    MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                        new Message("NetworkMessagingAutomaton", "Received message:\r\nFrom: " + stringFrom +
                        "\r\nTo: " + stringTo + "\r\nData: " + stringData + "\r\n"));

                    messageRouter.Send(stringTo, new Message(stringFrom, stringData));
                }
            }
            catch (Exception e)
            {
                AddMessage(new Message("NetworkMessagingAutomaton", Errors.PULL_ERROR));
                MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                    new Message("NetworkMessagingAutomaton", "An error occured while pulling a message."));
            }
        }

        protected void PushMessages()
        {
            try
            {
                Message message;
                byte[] from, to, data;
                byte[] fromLength = new byte[4], toLength = new byte[4], dataLength = new byte[4];
                while (pushRunning)
                {
                    message = messagesToSend.Remove();
                    if (message == null)
                    {
                        return;
                    }
                    from = Encoding.UTF8.GetBytes(message.From);
                    to = Encoding.UTF8.GetBytes(((String[])message.Data)[0]);
                    data = Encoding.UTF8.GetBytes(((String[])message.Data)[1]);

                    fromLength[0] = (byte)(from.Length >> 24);
                    fromLength[1] = (byte)(from.Length >> 16);
                    fromLength[2] = (byte)(from.Length >> 8);
                    fromLength[3] = (byte)from.Length;

                    toLength[0] = (byte)(to.Length >> 24);
                    toLength[1] = (byte)(to.Length >> 16);
                    toLength[2] = (byte)(to.Length >> 8);
                    toLength[3] = (byte)to.Length;

                    dataLength[0] = (byte)(data.Length >> 24);
                    dataLength[1] = (byte)(data.Length >> 16);
                    dataLength[2] = (byte)(data.Length >> 8);
                    dataLength[3] = (byte)data.Length;

                    outputStream.Write(fromLength);
                    outputStream.Write(toLength);
                    outputStream.Write(dataLength);
                    outputStream.Write(from);
                    outputStream.Write(to);
                    outputStream.Write(data);
                }
            }
            catch (Exception e)
            {
                AddMessage(new Message("NetworkMessagingAutomaton", Errors.PUSH_ERROR));
                MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                    new Message("NetworkMessagingAutomaton", "An error occured while pushing a message."));
            }
        }
    }
}
